Making blocking calls to async
methods transforms code that was intended to be asynchronous into a blocking operation. Doing so can
cause deadlocks and unexpected blocking of context threads.
According to the MSDN documentation:
The root cause of this deadlock is due to the way await
handles contexts. By default, when an incomplete Task
is
awaited, the current “context” is captured and used to resume the method when the Task
completes. This “context” is the current
SynchronizationContext
unless it’s null, in which case it’s the current TaskScheduler
. GUI and ASP.NET applications have a
SynchronizationContext
that permits only one chunk of code to run at a time. When the await
completes, it attempts to
execute the remainder of the async
method within the captured context. But that context already has a thread in it, which is
(synchronously) waiting for the async
method to complete. They’re each waiting for the other, causing a deadlock.
To Do This … |
Instead of This … |
Use This |
Retrieve the result of a background task |
Task.Wait , Task.Result or Task.GetAwaiter.GetResult
|
await
|
Wait for any task to complete |
Task.WaitAny
|
await Task.WhenAny
|
Retrieve the results of multiple tasks |
Task.WaitAll
|
await Task.WhenAll
|
Wait a period of time |
Thread.Sleep
|
await Task.Delay
|
Noncompliant code example
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait(); // Noncompliant
}
}
Compliant solution
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
public static async Task TestAsync()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
await delayTask;
}
}
Exceptions
- Main methods of Console Applications are not subject to this deadlock issue and so are ignored by this rule.
-
Thread.Sleep
is also ignored when it is used in a non-async
method.
- Calls chained after
Task.Run
or Task.Factory.StartNew
are ignored because they don’t suffer from this deadlock issue